//
// Created by Key-Box-Min on 24/06/2020.
//

#ifndef COMPILERDOMEA1_ATOMICLOCK_H
#define COMPILERDOMEA1_ATOMICLOCK_H

#include <atomic>
#include <thread>
#include <functional>
#include <future>

class Guard {
public:
    enum class Option{
        create,
        destroy
    };
    Guard(std::function<void ()> create,std::function<void ()> destroy)
            :__create(create),__destroy(destroy){
        __create();
    }
    Guard(std::function<void ()> callback,Option option){
        if(option == Option::create){
            __create = callback;
            __create();
        }else{
            __destroy = callback;
        }
    }

    void delDestroy(){
        static auto f = [](){};
        __destroy = f;
    }

    virtual ~Guard(){
        __destroy();
    }

protected:
    std::function<void ()> __create;
    std::function<void ()> __destroy;
};

class AtomicLock{
public:
    enum class Status{
        unlock,
        lock,
        readLock
    };

    AtomicLock():
            __status(Status::unlock),
            __readCount(0){}

    void lock(){
        if(__status == Status::lock && __lock_thread_id == std::this_thread::get_id())return;
        realLock();
        __status = Status::lock;
    }

    void realLock(){
        while (__flag.test_and_set())
            std::this_thread::yield();
        __lock_thread_id = std::this_thread::get_id();
    }

    void unlock(){
        if(__status == Status::lock)__unlock();
    }

    bool try_Lock(){
        return !__flag.test_and_set();
    }

    void readLock(){
        Guard l([&](){while(__readflag.test_and_set());},[&](){__readflag.clear();});
        if (__readCount == 0){
            realLock();
            __status = Status::readLock;
        }
        if(__status == Status::readLock){
            __readCount++;
        }
    }

    void readUnlock(){
        Guard l([&](){while(__readflag.test_and_set());},[&](){__readflag.clear();});
        if(__status == Status::readLock){
            __readCount--;
            if(__readCount == 0)__unlock();
        }
    }

    int readCount(){return __readCount;}
    Status status(){return __status;}
    const std::thread::id& getCurrentThreadId(){return __lock_thread_id;}

protected:
    void __unlock(){
        __flag.clear();
        __lock_thread_id = __default_thread_id;
        __status = Status::unlock;
    }
private:
    std::atomic_flag __flag = ATOMIC_FLAG_INIT;
    std::atomic_flag __readflag = ATOMIC_FLAG_INIT;
    std::atomic_int  __readCount;
    std::atomic<Status> __status;
    std::thread::id __lock_thread_id;
    const std::thread::id __default_thread_id;
};

class AtomicLock_guard : public Guard{
public:
    enum class Optionl{unlock};
    AtomicLock_guard(AtomicLock* lock):Guard(
            std::bind(&AtomicLock::lock,lock),
            std::bind(&AtomicLock::unlock,lock)){}
    AtomicLock_guard(AtomicLock* lock,Optionl option):Guard(
            std::bind(&AtomicLock::unlock,lock),
            Option::destroy){}
};

class AtomicLock_Readguard : public Guard{
public:
    enum class Optionl{unlock};
    AtomicLock_Readguard(AtomicLock* lock):Guard(
            std::bind(&AtomicLock::readLock,lock),
            std::bind(&AtomicLock::readUnlock,lock)){}
    AtomicLock_Readguard(AtomicLock* lock,Optionl option):Guard(
            std::bind(&AtomicLock::readUnlock,lock),
            Option::destroy){}
};


#endif //COMPILERDOMEA1_ATOMICLOCK_H
